/**
 * (created at 2011-1-27)
 */
package qiushuo.mysqlparser.recognizer.parser;

import static qiushuo.mysqlparser.recognizer.SQLTokenType.IDENTIFIER;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_BY;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_DEFAULT;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_LIMIT;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_ORDER;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_SET;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_UPDATE;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_WHERE;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.LITERAL_NUMBER;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.SC_QUESTION;

import java.sql.SQLSyntaxErrorException;
import java.util.LinkedList;
import java.util.List;

import qiushuo.mysqlparser.ast.expression.Expression;
import qiushuo.mysqlparser.ast.expression.primary.DefaultValue;
import qiushuo.mysqlparser.ast.expression.primary.ParamMarker;
import qiushuo.mysqlparser.ast.fragment.Limit;
import qiushuo.mysqlparser.ast.fragment.OrderBy;
import qiushuo.mysqlparser.ast.fragment.tableref.TableReferences;
import qiushuo.mysqlparser.ast.statement.DMLUpdateStatement;
import qiushuo.mysqlparser.recognizer.SQLToken;
import qiushuo.mysqlparser.recognizer.TokenReader;
import qiushuo.mysqlparser.util.Pair;

/**
 * @author <a href="mailto:QiuShuo1985@gmail.com">QIU Shuo</a>
 */
public class DMLUpdateStmtParser extends DMLParser {

    public DMLUpdateStmtParser(TokenReader input) {
        super(input);
    }

    /**
     * nothing has been pre-consumed <code><pre>
     * 'UPDATE' 'LOW_PRIORITY'? 'IGNORE'? table_reference
     *   'SET' colName ('='|':=') (expr|'DEFAULT') (',' colName ('='|':=') (expr|'DEFAULT'))*
     *     ('WHERE' cond)?
     *     {singleTable}? => ('ORDER' 'BY' orderBy)?  ('LIMIT' count)?
     * </pre></code>
     */
    public DMLUpdateStatement updateStmt() throws SQLSyntaxErrorException {
        match(KW_UPDATE);
        boolean low = false;
        boolean ignore = false;
        SQLToken token;
        loop1: while (true) {
            token = input.next();
            if (token == null) throw err("unexpected EOF after UPDATE");
            switch (token.getType()) {
            case KW_LOW_PRIORITY:
                if (low) err("duplicated mode for UPDATE: " + token);
                low = true;
                continue;
            case KW_IGNORE:
                if (ignore) err("duplicated mode for UPDATE: " + token);
                ignore = true;
                continue;
            default:
                input.backForce();
                break loop1;
            }
        }
        TableReferences tableRef = tableRefs();
        match(KW_SET);
        List<Pair<String, Expression>> values = assignColumnsValues();
        Expression where = null;
        OrderBy order = null;
        Limit limit = null;
        if (input.isNext(KW_WHERE, 0)) {
            input.skipForce(1);
            where = exprParser.expression();
        }
        if (input.isNext(KW_ORDER, 0) && input.isNext(KW_BY, 1)) {
            input.skipForce(2);
            order = orderBy();
        }
        if (input.isNext(KW_LIMIT, 0)) {
            input.skipForce(1);
            if (input.isNext(SC_QUESTION, 0)) {
                input.skipForce(1);
                limit = new Limit("0", new ParamMarker());
            } else {
                limit = new Limit("0", match(LITERAL_NUMBER).getText());
            }
        }
        return new DMLUpdateStatement(low, ignore, tableRef, values, where, order, limit);
    }

    /**
     * first <code>SET</code> has been consumed<br/>
     * at least one assignment
     * 
     * @throws SQLSyntaxErrorException
     */
    protected List<Pair<String, Expression>> assignColumnsValues() throws SQLSyntaxErrorException {
        List<Pair<String, Expression>> columnValues = new LinkedList<Pair<String, Expression>>();
        while (true) {
            SQLToken token = match(IDENTIFIER);
            String colName = token.getText();
            token = input.next();
            if (token == null) err("unexpected EOF");
            switch (token.getType()) {
            case OP_ASSIGN:
            case OP_EQUALS:
                break;
            default:
                err("unexpected token: " + token);
            }
            if (input.isNext(KW_DEFAULT, 0)) {
                input.skipForce(1);
                columnValues.add(new Pair<String, Expression>(colName, new DefaultValue()));
            } else {
                Expression expr = exprParser.expression();
                columnValues.add(new Pair<String, Expression>(colName, expr));
            }
            if ((token = input.next()) == null) {
                return columnValues;
            }
            switch (token.getType()) {
            case PUNC_COMMA:
                continue;
            default:
                input.backForce();
                return columnValues;
            }
        }
    }
}
